(*****************************************************************************
 * Delphi Plug-in Framework (DPF)
 * Copyright (C) 2006-2007 Tao Yuan
 *
 * The contents of this file are subject to the Mozilla Public License
 * Version 1.1 (the "License"); you may not use this file except in compliance
 * with the License. You may obtain a copy of the
 * License at http://www.mozilla.org/MPL/
 *
 * Software distributed under the License is distributed on an "AS IS" basis,
 * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License for
 * the specific language governing rights and limitations under the License.                                                               }
 *
 * The Initial Developer of the Original Code is Tao Yuan. Portions created by
 * Tao Yuan are Copyright (C) Tao Yuan (taoy.cn att gmail dott com)
 * All rights reserved.
 *****************************************************************************)
unit RDPluginManager;


interface

uses
  SysUtils, Classes,
  Rcl,
  RdClasses,
  RDPluginRegistry, RDExceptions, RDPluginService, RDPathResolver, LogLab;

type
  TPluginManager = class;
  TPluginLoader = class;


  (**
   * This is base for "home" class of plug-in runtime. Using this class,
   * plug-in code can get access to plug-in framework
   * ({@link org.java.plugin.PluginManager manager},
   * {@link org.java.plugin.PluginRegistry registry}) which was loaded it.
   * It is also used by manager during plug-in life cycle management (activation
   * and deactivation).
   * <br>
   * Plug-in vendor may provide it's own implementation of this class if some
   * actions should be performed during plug-in activation/deactivation. When no
   * class specified, framework provides default "empty" implementation that does
   * nothing when plug-in started and stopped.
   * @version $Id: Plugin.java,v 1.1 2004/11/30 18:45:20 ddimon Exp $
   *)

  TPluginInfo = class(TRdLogPersistent, IPlugin, IPluginInfo)
  private
    started: Boolean;

    FPluginLoader:TPluginLoader;
    FDescriptor: IPluginDescriptor;
    FManager: TPluginManager;
    FPlugin: IPlugin;

  protected
    (**
     * This method will be called once during plug-in activation before any
     * access to any code from this plug-in.
     * @throws Exception if an error has occurred during plug-in start-up
     *)
    procedure doStart;
    (**
     * This method will be called once during plug-in deactivation. After
     * this method call, no other code from this plug-in can be accessed,
     * unless {@link #doStart()} method will be called again (but for another
     * instance of this class).
     * @throws Exception if an error has occurred during plug-in shutdown
     *)
    procedure doStop;
    function getDescriptor: IPluginDescriptor;
    function GetPlugin: IPlugin;
    procedure setDescriptor(Value: IPluginDescriptor);
    procedure SetPlugin(const Value: IPlugin);
    function isActive: Boolean;      
    { IPlugin }
    function getName: string; stdcall;
    procedure Initialize(APluginService: IPluginService; APluginInfo: IPluginInfo);
        stdcall;
    procedure Start; stdcall;
    procedure Stop; stdcall;

  public
    constructor Create(APlugin: IPlugin); virtual;
    destructor Destroy; override;
  end;

//  TPluginClass = class of TPluginInfo;

  (**
   * Plug-ins life-cycle events callback interface.
   * @version $Id: PluginManager.java,v 1.11 2005/03/19 10:49:19 ddimon Exp $
   *)
  IEventListener = interface(IInterface)
      (**
       * This method will be called by the manager just after plug-in
       * has been successfully activated.
       * @param plugin just activated plug-in
       *)
      procedure pluginActivated(plugin: IPluginInfo);

      (**
       * This method will be called by the manager just before plug-in
       * deactivation.
       * @param plugin plug-in to be deactivated
       *)
      procedure pluginDeactivated(plugin: IPluginInfo);

      (**
       * This method will be called by the manager just before plug-in
       * disabling.
       * @param descriptor descriptor of plug-in to be disabled
       *)
      procedure pluginDisabled(descriptor: IPluginDescriptor);
        
      (**
       * This method will be called by the manager just after plug-in
       * enabling.
       * @param descriptor descriptor of enabled plug-in
       *)
      procedure pluginEnabled(descriptor: IPluginDescriptor);
  end;

  IPluginLocation = interface(IInterface)
  ['{FAE539D9-8EF6-407E-A7FC-F5BBB13DDAAD}']
    (**
     * @return location of plug-in manifest
     *)
    function getManifestLocation: string;
        
    (**
     * @return location of plug-in context ("home")
     *)
    function getContextLocation: string;
  end;

  //PluginLocations = array of IPluginLocation;

  TEventListenerAdapter = class (TRdLogPersistent, IEventListener)
  public
    procedure pluginActivated(plugin: IPluginInfo);
    procedure pluginDeactivated(plugin: IPluginInfo);
    procedure pluginDisabled(descriptor: IPluginDescriptor);
    procedure pluginEnabled(descriptor: IPluginDescriptor);
  end;

  TRegistryChangeListener = class(TRdLogPersistent, IRegistryChangeListener)
    procedure registryChanged(data: IRegistryChangeData); stdcall;
  private
    pluginManager: TPluginManager;
  public
    constructor Create(APluginManager: TPluginManager);
  end;

  TRegistryFactory = class(TObject)
  private
    log: ILog;
    cl: TPluginLoader;
    isValidating,
    stopOnError: Boolean;
    registryClass: String;
    props: TProperties;// TProperties;
    function getServiceProperty(name: string): string;
    procedure loadProps;
    function lookupProperty(name: string): string;
  public
    constructor Create;
    function newRegistry: IPluginRegistry;
  end;

  (**
   * DPF "runtime" class - the entry point to the framework API. It is
   * expected that only one instance of this class will be created per
   * application (other scenarios may be possible but not tested).
   *
   * @see org.java.plugin.ObjectFactory#createManager()
   *
   * @version $Id: PluginManager.java,v 1.18 2005/09/20 16:56:47 ddimon Exp $
   *)

  TPluginManager = class (TRdLogPersistent)
  private
  protected
    FPluginService: IPluginService;
    procedure disposeClassLoader(cl: TPluginLoader);

    (**
     * Initializes given plug-in with this manager instance and given
     * descriptor.
     * @param plugin plug-in instance to be initialized
     * @param descr plug-in descriptor
     *)
    procedure initPlugin(plugin: IPluginInfo; descr: IPluginDescriptor);

    procedure notifyClassLoader(cl: TPluginLoader);

    (**
     * Starts given plug-in.
     * @param plugin plug-in to be started
     * @throws Exception if any error has occurred during plug-in start
     *)
    procedure startPlugin(plugin: IPluginInfo);

    (**
     * Stops given plug-in.
     * @param plugin plug-in to be stopped
     * @throws Exception if any error has occurred during plug-in stop
     *)
    procedure stopPlugin(plugin: IPluginInfo);

  public
    (**
     * Activates plug-in with given ID if it is not activated yet.
     * @param id plug-in ID
     * @throws PluginLifecycleException if plug-in can't be found or activated
     *)
    function activatePlugin(id: string): IPluginInfo; virtual; abstract;

    (**
     * Deactivates plug-in with given ID if it has been successfully activated
     * before. Note that this method will effectively deactivate all plug-ins
     * that depend on the given plug-in.
     * @param id plug-in ID
     *)
    procedure deactivatePlugin(id: string); virtual; abstract;

    (**
     * Deactivates plug-in with given ID if it has been successfully activated
     * before. Note that this method will effectively deactivate all plug-ins
     * that depend on the given plug-in.
     * @param id plug-in ID
     *)
    function disablePlugin(descr: IPluginDescriptor): IIntfSet; virtual; abstract;

    (**
     * Enables plug-in (or plug-ins) in this manager instance.
     * @param descr descriptor of plug-in to be enabled
     * @param includeDependings if <code>true</code>, depending plug-ins will
     *                          be also enabled
     * @return descriptors of plug-ins that was actually enabled
     * @see #disablePlugin(PluginDescriptor)
     *)
    function enablePlugin(descr: IPluginDescriptor; includeDependings: Boolean):
        IIntfSet; virtual; abstract;

    (**
     * @return path resolver
     *)
    function getPathResolver: IPathResolver; virtual; abstract;

    (**
     * Looks for plug-in with given ID and activates it if it is not activated
     * yet. Note that this method will never return <code>null</code>.
     * @param id plug-in ID
     * @return found plug-in
     * @throws PluginLifecycleException if plug-in can't be found or activated
     *)    
    function getPlugin(id: string): IPluginInfo; virtual; abstract;
    (**
     * Returns instance of plug-in's class loader and not tries to activate
     * plug-in. Use this method if you need to get access to plug-in resources
     * and don't want to cause plug-in activation.
     * @param descr plug-in descriptor
     * @return class loader instance for plug-in with given descriptor
     *)
    function getPluginLoader(descr: IPluginDescriptor): TPluginLoader; virtual;
        abstract;

    (**
     * Looks for plug-in, given object belongs to.
     * @param obj any object that maybe belongs to some plug-in
     * @return plug-in or <code>null</code> if given object doesn't belong
     *         to any plug-in (possibly it is part of "host" application)
     *         and thus doesn't managed by the Framework directly
     *         or indirectly
     *)
    function getPluginFor(obj: TObject): IPluginInfo; virtual; abstract;

    (**
     * @return registry, used by this manager
     *)
    function getRegistry(): IPluginRegistry; virtual; abstract;

    (**
     * @param descr plug-in descriptor
     * @return <code>true</code> if plug-in disabled as it's activation fails
     *)
    function isBadPlugin(descr: IPluginDescriptor): Boolean; virtual; abstract;

    (**
     * @param descr plug-in descriptor
     * @return <code>true</code> if plug-in with given descriptor is activated
     *)
    function isPluginActivated(descr: IPluginDescriptor): boolean; virtual;
        abstract;

    (**
     * @param descr plug-in descriptor
     * @return <code>true</code> if plug-in is currently activating
     *)
    function isPluginActivating(descr: IPluginDescriptor): boolean; virtual;
        abstract;

    (**
     * @param descr plug-in descriptor
     * @return <code>true</code> if given plug-in is disabled in this manager
     *)
    function isPluginEnabled(descr: IPluginDescriptor): boolean; virtual;
        abstract;

    (**
     * Registers plug-ins and their locations with this plug-in manager. You
     * should use this method to register new plug-ins to make them available
     * for activation with this manager instance (compare this to
     * {@link PluginRegistry#register(URL[])} method that just makes plug-in's
     * meta-data available for reading and doesn't "know" where are things
     * actually located).
     * @param locations plug-in locations data
     * @return map where keys are manifest URL's and values are registered
     *         plug-ins or plug-in fragments, URL's for unprocessed manifests
     *         are not included
     * @throws JpfException if given plug-ins can't be registered or published
     *         (optional behavior)
     *)
    function publishPlugins(locations: IList): IStrIntfMap; virtual; abstract;

    (**
     * Registers plug-in manager event listener. If given listener has been
     * registered before, this method will throw an
     * {@link IllegalArgumentException}.
     * @param listener new manager event listener
     *)
    procedure registerListener(listener: IEventListener); virtual; abstract;


    (**
     * Shuts down the framework.
     * <br>
     * Calling this method will deactivate all active plug-ins in order,
     * reverse to the order they was activated. It also releases all resources
     * allocated by this manager (class loaders, plug-in descriptors etc.).
     * All disabled plug-ins will be marked as "enabled", all registered even
     * listeners will be unregistered.
     *)
    procedure shutdown();  virtual; abstract;

    (**
     * Unregisters manager event listener. If given listener hasn't been
     * registered before, this method will throw an
     * {@link IllegalArgumentException}.
     * @param listener registered listener
     *)
    procedure unregisterListener(listener: IEventListener); virtual; abstract;
    property PluginService: IPluginService read FPluginService write FPluginService;
    
  end;   

  TURLLoader = class(TObject)
  protected
    ucp: IStrList;
    function findResource(name: string): string; virtual;
  public
    function getResource(name: string): string;
  end;

  TPluginLoader = class(TURLLoader)
  private
//    manager: TPluginManager;
    descriptor: IPluginDescriptor;
    manager: TPluginManager;
  protected
  public
    constructor Create(aManager: TPluginManager; descr: IPluginDescriptor); virtual;
    function getPluginDescriptor: IPluginDescriptor;
    function getPluginManager: TPluginManager;
    function newPluginInstance: IPluginInfo; virtual; abstract;
    procedure pluginsSetChanged; virtual; abstract;
  end;

implementation

uses
  DPFLog,
  RclSysUtils;

constructor TPluginInfo.Create(APlugin: IPlugin);
begin
  inherited Create();
  FPlugin := APlugin;
end;

destructor TPluginInfo.Destroy;
begin
  NilNoRelease(FPlugin);
  inherited;
end;

procedure TPluginInfo.doStart;
begin
  FPlugin.Start;
end;

procedure TPluginInfo.doStop;
begin
  FPlugin.Stop;
end;

function TPluginInfo.getDescriptor: IPluginDescriptor;
begin
  Result := FDescriptor;
end;

function TPluginInfo.getName: string;
begin
  Result := FPlugin.getName;
end;

function TPluginInfo.GetPlugin: IPlugin;
begin
  Result := FPlugin;
end;

procedure TPluginInfo.Initialize(APluginService: IPluginService; APluginInfo:
    IPluginInfo);
begin
  FPlugin.Initialize(APluginService, APluginInfo);
end;

function TPluginInfo.isActive: Boolean;
begin
  Result := started;
end;

procedure TPluginInfo.setDescriptor(Value: IPluginDescriptor);
begin
  FDescriptor := Value;
end;

procedure TPluginInfo.SetPlugin(const Value: IPlugin);
begin
  if FPlugin <> Value  then
    FPlugin := Value;
end;

procedure TPluginInfo.Start;
begin
  if not started then
  begin
    doStart;
    started := True;
  end;
end;


procedure TPluginInfo.Stop;
begin
  if started then
  begin
    doStop;
    started := False;
  end;
end;

{ TEventListenerAdapter }

procedure TEventListenerAdapter.pluginDisabled(descriptor: IPluginDescriptor);
begin
  // no-op
end;

procedure TEventListenerAdapter.pluginEnabled(descriptor: IPluginDescriptor);
begin
  // no-op
end;

procedure TEventListenerAdapter.pluginDeactivated(plugin: IPluginInfo);
begin
  // no-op
end;

procedure TEventListenerAdapter.pluginActivated(plugin: IPluginInfo);
begin
  // no-op
end;

constructor TRegistryFactory.Create;
begin
  log := DPFLog.LogFactory.getLogger(Self.ClassName);
end;

function TRegistryFactory.getServiceProperty(name: string): string;
begin
  // TODO -cMM: TRegistryFactory.getServiceProperty default body inserted
  Result := '';
end;

procedure TRegistryFactory.loadProps;
begin
  // TODO -cMM: TRegistryFactory.loadProps default body inserted
end;

function TRegistryFactory.lookupProperty(name: string): string;
begin
  Result := '';
end;

function TRegistryFactory.newRegistry: IPluginRegistry;
begin
  Result := nil;
end;

{ TJdRegistryChangeListener }

constructor TRegistryChangeListener.Create(APluginManager: TPluginManager);
begin
  pluginManager := APluginManager;
  if APluginManager = nil then
    raise Exception.Create('APluginManager can not be nil!');
end;

procedure TRegistryChangeListener.registryChanged(data: IRegistryChangeData);
begin
//  pluginManager.registryChangeHandler(data);
end;

procedure TPluginManager.disposeClassLoader(cl: TPluginLoader);
begin
  cl.Free;
end;

procedure TPluginManager.initPlugin(plugin: IPluginInfo; descr:
    IPluginDescriptor);
begin
//  plugin.setManager(Self);
  (plugin as IPlugin).Initialize(FPluginService, plugin);
  plugin.setDescriptor(descr);
end;

procedure TPluginManager.notifyClassLoader(cl: TPluginLoader);
begin
  cl.pluginsSetChanged();
end;

procedure TPluginManager.startPlugin(plugin: IPluginInfo);
begin
  (plugin as IPlugin).Start;
end;

procedure TPluginManager.stopPlugin(plugin: IPluginInfo);
begin
  (plugin as IPlugin).Stop;
end;

constructor TPluginLoader.Create(aManager: TPluginManager; descr:
    IPluginDescriptor);
begin
  manager := aManager;
  descriptor := descr;
end;

function TPluginLoader.getPluginDescriptor: IPluginDescriptor;
begin
  Result := descriptor;
end;

function TPluginLoader.getPluginManager: TPluginManager;
begin
  Result := manager;
end;

function TURLLoader.findResource(name: string): string;
begin
  Result := '';
end;

function TURLLoader.getResource(name: string): string;
begin
  Result := findResource(name);
end;

{ TPluginInfo }

(**
 * Constructor to be used by plug-in manager for plug-in instantiation.
 * Your plug-ins have to provide constructor with this exact signature to
 * be successfully loaded by manager.
 * @param aManager manager, which controls this plug-in
 * @param descr descriptor of this plug-in
 *)

end.
